/*
 * This is a concrete class representing a Tetris piece
 */
package sirtet.tetris;

import java.awt.Dimension;
import sirtet.*;

/**
 *
 * @author guangwei.zhu
 */
public class TetrisPiece extends Piece<Integer> {

    // constants
    public static final int ROTATE_CLOCKWISE = 0;
    public static final int ROTATE_COUNTERCLOCKWISE = 1;
    public static final int FALLING = 3;    // Falling piece
    public static final int FRAGILE = 2;    // Fragile blocks can be eliminated permanently
    public static final int NORMAL = 1;     // Normal blocks are transferrable
    public static final int EMPTY = 0;
    protected int size;

    public Integer getEmptyElement()
    {
        return 0;
    }
    
    // Constructor is protected, allowing no one but the factory to generate
    protected TetrisPiece(int blocks) {
        super(blocks, blocks);

        size = blocks;
    }

    /**
     * Encode the TetrisPiece into a string of 0s and 1s
     * @return an encoded string containing information about the tetris
     */
    @Override
    public String toString() {
        int i, j;
        StringBuilder strb = new StringBuilder(size * size + 1);

        // A string composed of '0's and '1's
        for (i = 0; i < size; i++) {
            for (j = 0; j < size; j++) {
                strb.append(Integer.toString((Integer)this.pieceData[i][j]).trim());
            }
        }

        return strb.toString();
    }

    @Override
    public Object clone() {
        TetrisPiece newobj = new TetrisPiece(this.size);
        // Field-wise copy
        newobj.pieceData = this.cloneData();
        return newobj;
    }

    /**
     * If the piece is equivalent to another under rotation
     * @param piece the target piece to be checked
     * @return true if they are congruent; false if they are not equivalent
     * under rotation
     */
    public boolean isCongruent(TetrisPiece piece) {
        if (piece == null) {
            return false;
        }

        // Make copies so that the original ones are not altered.
        TetrisPiece inst1, inst2;
        int i;
        inst1 = (TetrisPiece) piece.clone();
        inst2 = (TetrisPiece) this.clone();

        inst1.align();
        inst2.align();
        for (i = 0; i < 4; i++) {
            if (inst1.equals(inst2)) {
                return true;
            }  // A equal situation found

            if (i == 3) {
                break;
            }  // Omit the last meaningless rotation
            inst2.rotate(ROTATE_CLOCKWISE);
        }

        return false;  // No equal situation is found
    }

    /**
     * Get the size, i.e., number of squares in this piece
     * @return the number of squares in the piece
     */
    public int getSize() {
        return size;
    }

    /**
     * Rotate clockwise and align the piece.
     */
    public void rotate() {
        this.rotate(TetrisPiece.ROTATE_CLOCKWISE, true);
    }

    /**
     * Rotate and align the piece.
     * @param orientation Use ROTATE_* constants to specify orientation
     */
    public void rotate(int orientation) {
        this.rotate(orientation, true);
    }

    /**
     * Rotate and (optionally) align the piece.
     * @param orientation Use ROTATE_* constants to specify orientation
     * @param aligned whether after rotation the piece is automatically aligned
     */
    public void rotate(int orientation, boolean aligned) {
        int i, j;
        Object[][] dataCopy = this.cloneData();

        switch (orientation) {
            case ROTATE_CLOCKWISE:

                for (i = 0; i < size; i++) {
                    for (j = 0; j < size; j++) {
                        pieceData[i][j] = dataCopy[size - 1 - j][i];
                    }
                }
                break;
            case ROTATE_COUNTERCLOCKWISE:

                for (i = 0; i < size; i++) {
                    for (j = 0; j < size; j++) {
                        pieceData[i][j] = dataCopy[j][size - 1 - i];
                    }
                }
                break;
        }

        if (aligned) {
            this.align();
        }
    }

    /**
     * Bring the piece to the top-left-most position within its field.
     */
    protected void align() {
        int i, j;
        int mini, minj;

        mini = size;
        minj = size;

        // Determining how much to shift
        for (i = 0; i < size; i++) {
            for (j = 0; j < size; j++) {
                if (i < mini || j < minj) {
                    if ((Integer)pieceData[i][j] != EMPTY) {
                        if (i < mini) {
                            mini = i;
                        }
                        if (j < minj) {
                            minj = j;
                        }
                    }
                }
            }
        }

        // Shifting piece
        for (i = 0; i < size; i++) {
            for (j = 0; j < size; j++) {
                if (i + mini < size && j + minj < size) {
                    // Translation
                    pieceData[i][j] = pieceData[i + mini][j + minj];
                } else {
                    pieceData[i][j] = EMPTY;
                }
            }
        }
    }

    /**
     * Rotate to a normalized position which all its congruent counterparts
     * share
     */
    public void normalize() {
        String encoded = "", norm = "";
        TetrisPiece temp = (TetrisPiece) this.clone();
        int i;

        for (i = 0; i < 4; i++) {
            encoded = temp.toString();
            if (encoded.compareTo(norm) > 0) {
                this.pieceData = temp.cloneData();
                norm = encoded;
            }

            temp.rotate(ROTATE_CLOCKWISE);
        }
    }

    @Override
    public int getWidth()
    {
        return getMinimumSize().width;
    }

    @Override
    public int getHeight()
    {
        return getMinimumSize().height;
    }

    /**
     * Get the size of the smallest rectangle at top-left corner which covers
     * the non-empty area of the piece.
     */
    public Dimension getMinimumSize() {
        int x, y;
        int mx, my;

        mx = 0;
        my = 0;
        for (y = 0; y < size; y++) {
            for (x = 0; x < size; x++) {
                if ((Integer)this.pieceData[y][x] != TetrisPiece.EMPTY)
                {
                    mx = Math.max(x, mx);
                    my = Math.max(y, my);
                }
            }
        }
        
        return new Dimension(mx + 1, my + 1);
    }
}
